#include <algorithm>
#include <cctype>
#include <sstream>
#include <stdexcept>
#include <string>

#include "query_helper.h"

namespace Common
{

QueryHelper::QueryHelper(const std::string& query)
    : m_query(query)
    , m_data(parse(query))
{
}

std::string QueryHelper::get(const std::string& key, const std::string& defaultValue) const
{
    if (m_data.count(key))
        return m_data.at(key);

    return defaultValue;
}

bool QueryHelper::getBoolean(
    const std::string& key,
    const bool defaultValue
) const
{
    if (!m_data.count(key))
        return defaultValue;

    auto value = m_data.at(key);
    std::transform(value.begin(), value.end(), value.begin(), ::tolower);

    if ("on" == value)
        return true;
    if ("yes" == value)
        return true;
    if ("true" == value)
        return true;
    return false;
}

int QueryHelper::getInteger(
    const std::string& key,
    const int defaultValue
) const
{
    if (!m_data.count(key))
        return defaultValue;

    try
    {
        return std::stoi(m_data.at(key));
    }
    catch (...)
    {
        return defaultValue;
    }
}

std::vector<std::string> QueryHelper::keys() const
{
    std::vector<std::string> result;
    result.reserve(m_data.size());
    for (const auto& pair : m_data)
        result.push_back(pair.first);
    return result;
}

std::unordered_map<std::string, std::string> QueryHelper::data() const
{
    return m_data;
}

std::string QueryHelper::query() const
{
    return m_query;
}

void QueryHelper::split(
    const std::string& value,
    char delimiter,
    std::vector<std::string>& elements
) const
{
    std::stringstream sstream;
    sstream.str(value);
    std::string item;
    while (std::getline(sstream, item, delimiter))
        elements.push_back(item);
}

void QueryHelper::decodeUrl(const std::string& in, std::string& out) const
{
    out.clear();
    out.reserve(in.size());
    for (std::size_t i = 0; i < in.size(); ++i)
    {
        const auto item = in[i];
        if (item == '%')
        {
            if ((i + 3) > in.size())
                return;

            int value = 0;
            std::istringstream istream(in.substr(i + 1, 2));

            if (istream >> std::hex >> value)
            {
                out += static_cast<char>(value);
                i += 2;
            }
            else
            {
                return;
            }
        }
        else if (item == '+')
        {
            out += ' ';
        }
        else
        {
            out += item;
        }
    }
}

std::unordered_map<std::string, std::string> QueryHelper::parse(
    const std::string& requestBody
) const
{
    std::unordered_map<std::string, std::string> body;
    std::vector<std::string> container;
    split(requestBody, '&', container);

    for (const auto& keyValue : container)
    {
        const auto position = keyValue.find("=");
        if (position == std::string::npos)
            continue;

        const auto key = keyValue.substr(0, position);
        const auto value = keyValue.substr(position + 1);
        decodeUrl(value, body[key]);
    }

    return body;
}

std::unordered_map<std::string, std::string> queryMap(const std::string& query)
{
    return QueryHelper(query).data();
}

std::ostream& operator<<(std::ostream& out, const QueryHelper& helper)
{
    const auto data = helper.data();
    out << "---" << std::endl
        << "QueryHelper" << std::endl
        << "value: " << helper.query() << std::endl
        << "size: " << data.size() << std::endl;

    for (const auto& [key, value] : data)
    {
        out << "---" << std::endl;
        out << key << std::endl
            << value << std::endl;
    }

    out << "---" << std::endl;
    return out;
}

} // namespace Common
